﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
using System.Windows.Forms;

namespace VisionarySystems.TimeRegi
{
	#region ITimePeriod Interface
	public interface ITimePeriode
	{
		TimeSpan WorkedTime { get; }
		int TimeSlotCount { get; }
		int SubClassCount { get; }
		int Year { get; }
		TimePeriodeID ID { get; set; }

		void Add(IEnumerable<TimeSlot> TimeSlots);
		TreeNode GetNode();
		bool AssembleTimeslots(out List<TimeSlot> NewTimeSlots, out List<int> RemovedIndexes);
		TimePeriodeType GetTypeID();
		ITimePeriode GetTimePeriode(TimePeriodeID ID);
		IEnumerator<TimeSlot> TimeSlots();
	}
	#endregion

	public enum TimePeriodeType {Unknown = 0, TimeSlot = 1, Workday= 2, WorkWeek=3, WorkMonth = 4, WorkYear= 5, Controller= 10} ;

	#region TimeSlot
	
	public class TimeSlot : ITimePeriode, IEnumerable<TimeSlot>
	{
		#region Private Members
		int m_Idx;
		DateTime m_Start;
		DateTime m_Stop;
		TimeSlotTypes m_Type;
		private TimePeriodeID m_ID;

		#endregion

		#region Ctor

		public TimeSlot(int Idx, DateTime Start, DateTime Stop, TimeSlotTypes Type)
		{
			m_Idx = Idx;
			m_Start = Start;
			m_Stop = Stop;
			m_Type = Type;
		}

		#endregion

		#region Properties

		public TimePeriodeID ID
		{
			get { return m_ID; }
			set { m_ID = value; }
		}

		public DateTime Start { get { return m_Start; } }
		
		public DateTime Stop { get { return m_Stop; } }

		public int Idx { get { return m_Idx; } }

		public TimeSpan WorkedTime
		{
			get
			{
				return m_Stop.Subtract(m_Start);
			}
		}

		public int TimeSlotCount
		{
			get
			{
				return 1;
			}

		}

		public int SubClassCount
		{
			get
			{
				return 0;
			}
		}

		public int Year
		{
			get { return m_Start.Year; }
		}

		public int Month
		{
			get { return m_Start.Month; }
		}

		public int Week
		{
			get 
			{
				DateTimeFormatInfo dfi = DateTimeFormatInfo.CurrentInfo;
				Calendar cal = dfi.Calendar;

				int WeekNo = cal.GetWeekOfYear(m_Start,
												dfi.CalendarWeekRule,
												dfi.FirstDayOfWeek);
				return WeekNo;
			}
		}

		public DateTime Date
		{
			get { return m_Start.Date; }
		}


		#endregion

		#region Public methods

		public static string GetTime(DateTime d)
		{
			return string.Format("{0:D2}:{1:D2}", d.Hour, d.Minute);
		}

		public static string GetHours(TimeSpan ts)
		{
			return string.Format("{0:D1},{1:D1}", ts.Hours, (int)(10*ts.Minutes/60));
		}

		public TreeNode GetNode()
		{
			TreeNode node = new TreeNode(this.ToString());
			node.Tag = this.ID;
			return node;
		}

		public ITimePeriode GetTimePeriode(TimePeriodeID ID)
		{
			ITimePeriode result = null;
			if (ID == this.ID)
				result = this as ITimePeriode;
			return result;
		}


		public override string ToString()
		{
			return string.Format("{0} - {1} ({2})", GetTime(m_Start), GetTime(m_Stop), GetHours( WorkedTime));
		}
		
/*        public void Add(ITimePeriode NewPeriode)
		{
			throw new ArgumentException("It is not allowed to at a period to a TimeSlot");
		}*/

		public void Add(IEnumerable<TimeSlot> TimeSlots)
		{
			throw new ArgumentException("It is not allowed to add a period to a TimeSlot");
		}

		/// <summary>
		/// It is not possible to assemble a timeslot
		/// </summary>
		/// <param name="RemovedIndexes"></param>
		public bool AssembleTimeslots(out List<TimeSlot> NewTimeSlots, out List<int> RemovedIndexes)
		{
			RemovedIndexes = new List<int>();
			NewTimeSlots = new List<TimeSlot>();
			return false;
		}

		public TimePeriodeType GetTypeID()
		{
			return TimePeriodeType.TimeSlot ;
		}

		public IEnumerator<TimeSlot> GetEnumerator()
		{
			yield return this;
			yield break;
		}

		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
		{
			yield return this;
			yield break;
		}

		public IEnumerator<TimeSlot> TimeSlots()
		{
			yield return this;
			yield break;
		}

		#endregion
	}

	#endregion


	#region TimePeriodHost

	public class TimePeriodHost <TKey> : IEnumerable<ITimePeriode>
	{
		#region Private members

		private TimePeriodeID m_ID;
		
		protected Dictionary<TKey, ITimePeriode> m_TimePeriods = new Dictionary<TKey, ITimePeriode>();
		
		#endregion

		#region properties

		public TimePeriodeID ID
		{
			get { return m_ID; }
			set { m_ID = value; }
		}

		public TimeSpan WorkedTime
		{
			get
			{
				TimeSpan result = new TimeSpan();
				foreach (ITimePeriode period in m_TimePeriods.Values)
				{
					result = result.Add(period.WorkedTime);
				}
				return result;
			}
		}

		public int TimeSlotCount
		{
			get
			{
				int result = 0;
				foreach (ITimePeriode period in m_TimePeriods.Values)
				{
					result += period.TimeSlotCount;
				}
				return result;
			}
		}

		public int SubClassCount
		{
			get
			{
				int result = 0;
				foreach (ITimePeriode period in m_TimePeriods.Values)
				{
					result += period.SubClassCount +1;
				}
				return result;
			}
		}

		#endregion

		#region Public Methods

		public static string GetHours(TimeSpan ts)
		{
			return string.Format("{0:D1},{1:D1}", ts.Hours, (int)(10 * ts.Minutes / 60));
		}

		public ITimePeriode GetTimePeriode(TimePeriodeID ID)
		{
			if (ID == null)
				throw new ArgumentNullException("ID is not allowed to be null");
			ITimePeriode result = null;
			if (ID == this.ID)
				result = this as ITimePeriode;
			else
			{
				foreach (ITimePeriode periode in m_TimePeriods.Values)
				{
					result = periode.GetTimePeriode(ID);
					if (result != null)
						break;
				}
			}
			return result;
		}

		public TreeNode GetNode()
		{
			TreeNode result = new TreeNode(this.ToString());
			result.Tag = this.ID;
			foreach (ITimePeriode periode in this)
				result.Nodes.Add(periode.GetNode());
			return result;
		}

		public virtual void Add(TKey Key, ITimePeriode NewPeriod)
		{
			NewPeriod.ID = GetNextID(NewPeriod);
			m_TimePeriods.Add(Key, NewPeriod);
		}

		protected TimePeriodeID GetNextID(ITimePeriode NewPeriod)
		{
			TimePeriodeID result = new TimePeriodeID(m_TimePeriods.Count, NewPeriod.GetTypeID());
			// Use the first bit 0-15 for Local Id 
			//int result = m_TimePeriods.Count;
			// Use bit 16 and up for type ID
			//int typeID = (int)NewPeriod.GetTypeID();
			//result += typeID * 0x1000;
			return result;
		}

		public virtual bool AssembleTimeslots(out List<TimeSlot> NewTimeSlots, out List<int> RemovedIndexes)
		{
			bool result = false;
			RemovedIndexes = new List<int>();
			NewTimeSlots = new List<TimeSlot> ();
			foreach (ITimePeriode periode in m_TimePeriods.Values)
			{
				List<int> localRemovedIndexes;
				List<TimeSlot> localNewTimeSlots;
				if (periode.AssembleTimeslots(out localNewTimeSlots, out localRemovedIndexes))
				{
					RemovedIndexes.AddRange(localRemovedIndexes);
					NewTimeSlots.AddRange(localNewTimeSlots);
					result = true;
				}
			}
			return result;
		}

		public TimePeriodeType GetTypeID()
		{
			TimePeriodeType result = TimePeriodeType.Unknown;
			if (this is WorkDay)
				result = TimePeriodeType.Workday;
			if (this is WorkWeek)
				result = TimePeriodeType.WorkWeek;
			if (this is WorkYear)
				result = TimePeriodeType.WorkYear;
			return result;
		}

		public TimePeriodeType GetSubTypeID()
		{
			TimePeriodeType result = TimePeriodeType.Unknown;
			if (this is WorkDay)
				result = TimePeriodeType.TimeSlot;
			if (this is WorkWeek)
				result = TimePeriodeType.Workday;
			if (this is WorkYear)
				result = TimePeriodeType.WorkWeek;
			if (this is TimePeriodController)
				result = TimePeriodeType.WorkYear;
			return result;
		}
		
		public IEnumerator<ITimePeriode> GetEnumerator()
		{
			foreach (ITimePeriode periode in m_TimePeriods.Values)
				yield return periode;
		}

		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
		{
			foreach (ITimePeriode periode in m_TimePeriods.Values)
				yield return periode;
		}

		#endregion

	}

	#endregion


	#region WorkDay

	public class WorkDay : TimePeriodHost<DateTime>, ITimePeriode
	{
		#region Private members

		//        SortedDictionary<DateTime, TimeSlot> m_TimeSlots = new SortedDictionary<DateTime, TimeSlot>();
		DateTime m_WorkDate;

		#endregion

		#region Ctor

		public WorkDay(DateTime WorkDate)
		{
			m_WorkDate = WorkDate.Date;
			if (m_WorkDate == null)
				throw new ArgumentOutOfRangeException("WorkDate must not be null");
		}

		#endregion

		#region properties

		public DateTime WorkDate { get { return WorkDate; } }

		public int Year
		{
			get { return m_WorkDate.Year; }
		}


		#endregion

		#region Public Methods

		public static string GetDate(DateTime d)
		{
			return string.Format("{0:D2}/{1:D2}", d.Day, d.Month);
		}
		
		public override string ToString()
		{
			return string.Format("{0} ({1})", GetDate(m_WorkDate), GetHours(WorkedTime));
		}

		public void Add(IEnumerable<TimeSlot> TimeSlots)
		{
			foreach (TimeSlot slot in TimeSlots)
			{
				m_TimePeriods.Remove(slot.Start);
				slot.ID = GetNextID(slot);
				m_TimePeriods.Add(slot.Start, slot);
			}
		}

		/// <summary>
		/// Assambles all timeslots to one timeslot, where th first start is the start
		/// And the last stop is the stop
		/// It will reuse the first Idx for the new TimeSlot
		/// </summary>
		/// <param name="RemovedIndexes">Returns a list of all the indexes of the timeslots that has been removed</param>
		public override bool AssembleTimeslots(out List<TimeSlot> NewTimeSlots, out List<int> RemovedIndexes)
		{
			//Init
			bool result = false ;
			try
			{
				RemovedIndexes = new List<int>();
				NewTimeSlots = new List<TimeSlot>();
				if (m_TimePeriods.Count <= 1)
					return result;
				DateTime start = DateTime.MaxValue;
				DateTime stop = DateTime.MinValue;
				//Find Min / Max Values
				foreach (TimeSlot slot in m_TimePeriods.Values)
				{
					RemovedIndexes.Add(slot.Idx);
					if (slot.Start < start)
						start = slot.Start;
					if (slot.Stop > stop)
						stop = slot.Stop;
				}
				//Remove old slots
				Clear();
				// Add the new slot
				TimeSlot newTimeSlot = new TimeSlot(RemovedIndexes[0], start, stop, TimeSlotTypes.WorkTime);
				NewTimeSlots.Add(newTimeSlot);
				Add(start, newTimeSlot);
				result = true;
			}
			finally
			{ }
			return result ;
		}

		private void Clear()
		{
			m_TimePeriods.Clear();
		}
		/*public void Add (TimeSlot NewSlot)
		{
			if (NewSlot.Start.Date == m_WorkDate)
				m_TimeSlots.Add(NewSlot.Start, NewSlot);
			else
				throw new ArgumentOutOfRangeException (string.Format ("StartDate must be the same as the work date: StartTime = {0}, WorkDate = {1}", NewSlot.Start, m_WorkDate.Date));
		}*/

		public IEnumerator<TimeSlot> TimeSlots()
		{
			foreach (ITimePeriode periode in m_TimePeriods.Values)
				yield return periode as TimeSlot;
		}

		#endregion
	}

	#endregion

	#region WorkDayHost
	public class WorkDayHost<TKey> : TimePeriodHost<TKey>
	{
		#region Private members

		//SortedDictionary<DateTime, WorkDay> m_WorkDays = new SortedDictionary<DateTime, WorkDay>();

		#endregion

		#region Ctor

		#endregion

		#region Properties

		#endregion

		#region Public methods

		#endregion
	}
	#endregion

	#region WorkWeek

	public class WorkWeek : WorkDayHost<DateTime>, ITimePeriode
	{
		#region Private members

		//SortedDictionary<DateTime, WorkDay> m_WorkDays = new SortedDictionary<DateTime, WorkDay>();
		int m_WeekNo;
		int m_Year;

		#endregion

		#region Ctor

		public WorkWeek(int WeekNo, int Year)
		{
			m_WeekNo = WeekNo;
			m_Year = Year;
			if ((m_WeekNo < 1) || (m_WeekNo > 53))
				throw new ArgumentOutOfRangeException("WeekNo must be between 1 and 53");
			if ((m_Year < 2011) || (m_Year > 2020))
				throw new ArgumentOutOfRangeException("Year must be between 2011 and 2020");
		}

		#endregion

		#region Properties

		public int Year
		{
			get { return m_Year; }
		}


		#endregion

		#region Public Methods

		public static string GetWeek(int WeekNo)
		{
			return string.Format("Uge {0:D2}", WeekNo);
		}

		public override string ToString()
		{
			return string.Format("{0} ({1})", GetWeek(m_WeekNo), GetHours(WorkedTime));
		}

		public void Add(IEnumerable<TimeSlot> TimeSlots)
		{
			foreach (TimeSlot slot in TimeSlots)
			{
				ITimePeriode currentDate;
				if (!m_TimePeriods.TryGetValue(slot.Date, out currentDate))
				{
					currentDate = new WorkDay(slot.Date);
					m_TimePeriods.Add(slot.Date, currentDate);
					currentDate.ID = GetNextID(currentDate);
				}
				currentDate.Add(slot);
			}
		}

		public IEnumerator<TimeSlot> TimeSlots()
		{
			foreach (ITimePeriode day in m_TimePeriods.Values)
				foreach (ITimePeriode slot in day as WorkDay)
					yield return slot as TimeSlot;
		}


		#endregion

	}

	#endregion

	/*#region WorkMonth
	public class WorkMonth : WorkDayHost, ITimePeriod
	{
		#region Private members

		//SortedDictionary<DateTime, WorkDay> m_WorkDays = new SortedDictionary<DateTime, WorkDay>();
		int m_MonthNo;
		int m_Year;

		#endregion

		#region Ctor

		public WorkMonth(int MonthNo, int Year)
		{
			m_MonthNo = MonthNo;
			m_Year = Year;
			if ((m_MonthNo < 1) || (m_MonthNo > 12))
				throw new ArgumentOutOfRangeException("MonthNo must be between 1 and 12");
			if ((m_Year < 2011) || (m_Year > 2020))
				throw new ArgumentOutOfRangeException("Year must be between 2011 and 2020");
		}

		#endregion

		#region Properties

		public int Year
		{
			get { return m_Year; }
		}


		#endregion
	}
	#endregion*/

	#region WorkYear
	public class WorkYear : TimePeriodHost<int>, ITimePeriode
	{
		#region Private members

		int m_Year;

		#endregion

		#region Ctor

		public WorkYear(int Year)
		{
			m_Year = Year;
			if ((m_Year < 2011) || (m_Year > 2020))
				throw new ArgumentOutOfRangeException("Year must be between 2011 and 2020");
		}

		#endregion

		#region Properties

		public int Year
		{
			get { return m_Year; }
		}


		#endregion

		#region Public Methods

		public override string ToString()
		{
			return string.Format("År {0}", m_Year);
		}
		
		public void Add(IEnumerable<TimeSlot> TimeSlots)
		{
			foreach (TimeSlot slot in TimeSlots)
			{
				ITimePeriode currentWeek;
				if (!m_TimePeriods.TryGetValue(slot.Week, out currentWeek))
				{
					currentWeek = new WorkWeek(slot.Week, m_Year);
					m_TimePeriods.Add(slot.Week, currentWeek);
					currentWeek.ID = GetNextID(currentWeek);
				}
				currentWeek.Add(slot);
			}
		}

		public IEnumerator<TimeSlot> TimeSlots()
		{
			foreach (ITimePeriode week in m_TimePeriods.Values)
				foreach (ITimePeriode day in week as WorkWeek)
					foreach (ITimePeriode slot in day as WorkDay)
						yield return slot as TimeSlot;
		}


		#endregion

	}

	#endregion

	#region TimePeriodController

	public class TimePeriodController : TimePeriodHost<int>, ITimePeriode
	{
		#region private members

		//Dictionary<int, WorkYear> m_Years = new Dictionary<int, WorkYear>();

		#endregion

		#region properties

		public int Year
		{
			get { throw new NotImplementedException(); }
		}
		
		#endregion

		#region Public Methods

		public void Add(IEnumerable<TimeSlot> TimeSlots)
		{
			foreach (TimeSlot slot in TimeSlots)
			{
				ITimePeriode currentYear;
				if (!m_TimePeriods.TryGetValue(slot.Year, out currentYear))
				{
					currentYear = new WorkYear(slot.Year);
					m_TimePeriods.Add(currentYear.Year, currentYear);
					currentYear.ID = GetNextID(currentYear);
				}
				currentYear.Add(slot);
			}
		}

		public IEnumerator<TimeSlot> TimeSlots()
		{
			foreach (ITimePeriode year in m_TimePeriods.Values)
				foreach (ITimePeriode week in year as WorkYear)
					foreach (ITimePeriode day in week as WorkWeek)
						foreach (ITimePeriode slot in day as WorkDay)
							yield return slot as TimeSlot;
		}


		#endregion

		public ITimePeriode GetTimePeriode(int p)
		{
			throw new NotImplementedException();
		}
	}

	#endregion

	#region TimePeriodeID

	public class TimePeriodeID
	{
		#region Constructor 

		public TimePeriodeID(int SubID, TimePeriodeType Type)
		{
			this.Type = Type;
			this.SubID = SubID;
		}

		#endregion

		#region Properties

		public TimePeriodeType Type { get; private set; }
		public int SubID { get; private set; }

		#endregion

		#region Public methods

		public override string ToString()
		{
			return string.Format("Type: {0}, SubID: {1}", Type, SubID);
		}

		public override bool Equals(System.Object obj)
		{
			// If parameter is null return false.
			if (obj == null)
			{
				return false;
			}

			// If parameter cannot be cast to Point return false.
			TimePeriodeID p = obj as TimePeriodeID;
			if ((System.Object)p == null)
			{
				return false;
			}

			// Return true if the fields match:
			return (Type == p.Type) && (SubID == p.SubID);
		}

		public bool Equals(TimePeriodeID p)
		{
			// If parameter is null return false:
			if ((object)p == null)
			{
				return false;
			}

			// Return true if the fields match:
			return (Type == p.Type) && (SubID == p.SubID);
		}

		#endregion

	}

	#endregion
}
